#include "stddef.h"
#include "cpuid.h"

const char * L2_associativity[0x10] = 
{
    "disabled", "direct mapped",
    "2-way", "reserved",
    "4-way", "reserved",
    "8-way", "reserved",
    "16-way", "reserved", "reserved",
    "reserved", "reserved", "reserved", "reserved",
    "fully associative"
};

static void parse_cpuid_features(void)
{
    cpuid.sse_max_major = 0; cpuid.sse_max_minor = 0;

	cpuid.features.FPU =	(cpuid.features_edx & (1 << 0)) == 0 ? false : true;
	cpuid.features.VME =	(cpuid.features_edx & (1 << 1)) == 0 ? false : true;
	cpuid.features.DE =		(cpuid.features_edx & (1 << 2)) == 0 ? false : true;
	cpuid.features.PSE =	(cpuid.features_edx & (1 << 3)) == 0 ? false : true;
	cpuid.features.TSC =	(cpuid.features_edx & (1 << 4)) == 0 ? false : true;
	cpuid.features.MSR =	(cpuid.features_edx & (1 << 5)) == 0 ? false : true;
	cpuid.features.PAE =	(cpuid.features_edx & (1 << 6)) == 0 ? false : true;
	cpuid.features.MCE =	(cpuid.features_edx & (1 << 7)) == 0 ? false : true;
	cpuid.features.CX8 =	(cpuid.features_edx & (1 << 8)) == 0 ? false : true;
	cpuid.features.APIC =	(cpuid.features_edx & (1 << 9)) == 0 ? false : true;
	cpuid.features.SEP =	(cpuid.features_edx & (1 << 11)) == 0 ? false : true;
	cpuid.features.MTRR =	(cpuid.features_edx & (1 << 12)) == 0 ? false : true;
	cpuid.features.PGE =	(cpuid.features_edx & (1 << 13)) == 0 ? false : true;
	cpuid.features.MCA =	(cpuid.features_edx & (1 << 14)) == 0 ? false : true;
	cpuid.features.CMOV =	(cpuid.features_edx & (1 << 15)) == 0 ? false : true;
	cpuid.features.PAT =	(cpuid.features_edx & (1 << 16)) == 0 ? false : true;
	cpuid.features.PSE_36 =	(cpuid.features_edx & (1 << 17)) == 0 ? false : true;
	cpuid.features.PSN =	(cpuid.features_edx & (1 << 18)) == 0 ? false : true;
	cpuid.features.CLFSH =	(cpuid.features_edx & (1 << 19)) == 0 ? false : true;
	cpuid.features.DS =		(cpuid.features_edx & (1 << 21)) == 0 ? false : true;
	cpuid.features.ACPI =	(cpuid.features_edx & (1 << 22)) == 0 ? false : true;
	cpuid.features.MMX =	(cpuid.features_edx & (1 << 23)) == 0 ? false : true;
	cpuid.features.FXSR =	(cpuid.features_edx & (1 << 24)) == 0 ? false : true;
	if((cpuid.features.SSE =	(cpuid.features_edx & (1 << 25)) == 0 ? false : true) == true){ cpuid.sse_max_major = 1; cpuid.sse_max_minor = 0; } 
	if((cpuid.features.SSE2 =	(cpuid.features_edx & (1 << 26)) == 0 ? false : true) == true){ cpuid.sse_max_major = 2; cpuid.sse_max_minor = 0; } 
	cpuid.features.SS =		(cpuid.features_edx & (1 << 27)) == 0 ? false : true;
	cpuid.features.HTT =	(cpuid.features_edx & (1 << 28)) == 0 ? false : true;
	cpuid.features.TM =		(cpuid.features_edx & (1 << 29)) == 0 ? false : true;
	cpuid.features.PBE =	(cpuid.features_edx & (1 << 31)) == 0 ? false : true;

	if((cpuid.features.SSE3 =		(cpuid.features_ecx & (1 << 0)) == 0 ? false : true) == true){ cpuid.sse_max_major = 3; cpuid.sse_max_minor = 0; } 
	cpuid.features.MONITOR =	(cpuid.features_ecx & (1 << 3)) == 0 ? false : true;
	cpuid.features.DS_CPL =		(cpuid.features_ecx & (1 << 4)) == 0 ? false : true;
	cpuid.features.VMX =		(cpuid.features_ecx & (1 << 5)) == 0 ? false : true;
	cpuid.features.SMX =		(cpuid.features_ecx & (1 << 6)) == 0 ? false : true;
	cpuid.features.EST =		(cpuid.features_ecx & (1 << 7)) == 0 ? false : true;
	cpuid.features.TM2 =		(cpuid.features_ecx & (1 << 8)) == 0 ? false : true;
	cpuid.features.SSSE3 =		(cpuid.features_ecx & (1 << 9)) == 0 ? false : true;
	cpuid.features.CNXT_ID =	(cpuid.features_ecx & (1 << 10)) == 0 ? false : true;
	cpuid.features.CMPXCHG16B =	(cpuid.features_ecx & (1 << 13)) == 0 ? false : true;
	cpuid.features.xTPR_Update_Control = (cpuid.features_ecx & (1 << 14)) == 0 ? false : true;
	cpuid.features.PDCM =		(cpuid.features_ecx & (1 << 15)) == 0 ? false : true;
	cpuid.features.DCA =		(cpuid.features_ecx & (1 << 18)) == 0 ? false : true;
	if((cpuid.features.SSE4_1 =		(cpuid.features_ecx & (1 << 19)) == 0 ? false : true) == true){ cpuid.sse_max_major = 4; cpuid.sse_max_minor = 1; } 
	if((cpuid.features.SSE4_2 =		(cpuid.features_ecx & (1 << 20)) == 0 ? false : true) == true){ cpuid.sse_max_major = 4; cpuid.sse_max_minor = 2; } 
	cpuid.features.POPCNT =		(cpuid.features_ecx & (1 << 23)) == 0 ? false : true;
}

static void parse_cpuid_ext_2(void)
{
    size_t eax, ebx, ecx, edx;
    
    eax = 0x80000002;
    asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));
    
    *(uint32_t*)(cpuid.cpu_brand + 0) = (uint32_t)eax;
    *(uint32_t*)(cpuid.cpu_brand + 4) = (uint32_t)ebx;
    *(uint32_t*)(cpuid.cpu_brand + 8) = (uint32_t)ecx;
    *(uint32_t*)(cpuid.cpu_brand + 12) = (uint32_t)edx;
    cpuid.cpu_brand[16] = 0;
    
    if(cpuid.max_ext_input >= 0x80000003)
    {
        eax = 0x80000003;
        asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));    

        *(uint32_t*)(cpuid.cpu_brand + 16) = (uint32_t)eax;
        *(uint32_t*)(cpuid.cpu_brand + 20) = (uint32_t)ebx;
        *(uint32_t*)(cpuid.cpu_brand + 24) = (uint32_t)ecx;
        *(uint32_t*)(cpuid.cpu_brand + 28) = (uint32_t)edx;
        cpuid.cpu_brand[32] = 0;
    }   
    
    if(cpuid.max_ext_input >= 0x80000004)
    {
        eax = 0x80000004;
        asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));    

        *(uint32_t*)(cpuid.cpu_brand + 32) = (uint32_t)eax;
        *(uint32_t*)(cpuid.cpu_brand + 36) = (uint32_t)ebx;
        *(uint32_t*)(cpuid.cpu_brand + 40) = (uint32_t)ecx;
        *(uint32_t*)(cpuid.cpu_brand + 44) = (uint32_t)edx;
        cpuid.cpu_brand[48] = 0;
    } 
}

static void parse_cpuid_ext_6(void)
{
    size_t eax, ebx, ecx, edx;
    
    eax = 0x80000006;
    asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));
    
    cpuid.l2_assoc = (uint8_t)((ecx >> 12) & 0xF);
    cpuid.l2_cache_size = (uint16_t)((ecx >> 16) & 0xFFFF);
    cpuid.l2_cache_line_size = (uint8_t)(ecx & 0xFF);
}

static void parse_cpuid_ext_0(void)
{
    size_t eax;
    
    eax = 0x80000000;
    asm volatile("cpuid;":"=a"(eax):"a"(eax));
    
    cpuid.max_ext_input = (uint32_t)eax;
}

static void parse_cpuid_basic_A(void)  
{
    size_t eax, ebx, ecx, edx;
        
    eax = 0x0A;
    asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));

	cpuid.perfmon.version = (uint8_t)(eax & 0xFF);
	
	/* if version == 0, performance monitoring is not supported, and lets set all the other values to 0 */
	if(!cpuid.perfmon.version)
	{
		eax = ebx = 0;
		ecx = edx = 0;
	}

	cpuid.perfmon.available_events = ebx;

	cpuid.perfmon.ncounters = (uint8_t)((eax >> 8) & 0xFF);
	cpuid.perfmon.counter_width = (uint8_t)((eax >> 16) & 0xFF);
	cpuid.perfmon.ebx_width = (uint8_t)((eax >> 24) & 0xFF);

	cpuid.perfmon.nC0 = (uint8_t)(edx & 0x0F);
	cpuid.perfmon.nC1 = (uint8_t)((edx >> 4) & 0x0F);
	cpuid.perfmon.nC2 = (uint8_t)((edx >> 8) & 0x0F);
	cpuid.perfmon.nC3 = (uint8_t)((edx >> 12) & 0x0F);
	cpuid.perfmon.nC4 = (uint8_t)((edx >> 16) & 0x0F);
}

static void parse_cpuid_basic_4(void)
{
    size_t eax, ebx, ecx, edx;
    size_t i;
   
    cpuid.cache_count = 0;
   
    for(i = 0; i < 32; i++)
    {
        eax = 4;
        ecx = i;
        asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));
        
        if(!(eax & 0x0F)){break;}
        
        cpuid.cache_info->type = (uint8_t)((eax >> 0) & 0x0F);
        cpuid.cache_info->level = (uint8_t)((eax >> 5) & 0x03);
        
        cpuid.cache_info->ways = (uint8_t)((ebx >> 22) & 0xFF);
        cpuid.cache_info->partitions = (uint8_t)((ebx >> 12) & 0xFF);
        cpuid.cache_info->line_size = (uint16_t)((ebx >> 0) & 0x3FF);
        cpuid.cache_info->sets = ecx;
        
        cpuid.cache_info->cache_size = (uint32_t)((uint32_t)(cpuid.cache_info->ways + 1) * 
                                                  (uint32_t)(cpuid.cache_info->partitions + 1) * 
                                                  (uint32_t)(cpuid.cache_info->line_size + 1) * 
                                                  (uint32_t)(cpuid.cache_info->sets + 1));
        cpuid.cache_count++;                                      
    }
}

static void parse_cpuid_basic_2(void)
{
    size_t eax, ebx, ecx, edx;
        
    eax = 2;
    asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));

}

static void parse_cpuid_basic_1(void)
{
    size_t eax, ebx, ecx, edx;
    
    eax = 1;
    asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));
    
    cpuid.features_ecx = ecx;
    cpuid.features_edx = edx;
    
    cpuid.stepping = (uint8_t)(eax & 0xF);
    cpuid.model = (uint8_t)((eax >> 4) & 0xF);
    cpuid.family = (uint8_t)((eax >> 8) & 0xF);
    cpuid.type = (uint8_t)((eax >> 12) & 0x3);
    cpuid.ext_model = (uint8_t)((eax >> 16) & 0xF);
    cpuid.ext_family = (uint8_t)((eax >> 20) & 0xFF);
    
    cpuid.cache_line_size = (uint16_t)(((ebx >> 8) & 0xFF) * 8);
}

static void parse_cpuid_basic_0(void)
{
    size_t eax, ebx, ecx, edx;
    
    eax = 0;
    asm volatile("cpuid;":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"a"(eax));

    cpuid.max_basic_input = (uint32_t)eax;
    *(uint32_t*)(cpuid.cpu_signature + 0) = (uint32_t)ebx;
    *(uint32_t*)(cpuid.cpu_signature + 4) = (uint32_t)edx;
    *(uint32_t*)(cpuid.cpu_signature + 8) = (uint32_t)ecx;
    cpuid.cpu_signature[12] = 0;
}

void parse_cpuid(void)
{
    cpuid.cpu_brand[0] = 0;
    
    parse_cpuid_basic_0();
    if(cpuid.max_basic_input >= 0x01){parse_cpuid_basic_1(); parse_cpuid_features();}
    if(cpuid.max_basic_input >= 0x02){parse_cpuid_basic_2();}
    if(cpuid.max_basic_input >= 0x04){parse_cpuid_basic_4();}
	parse_cpuid_basic_A();

    parse_cpuid_ext_0();
    if(cpuid.max_ext_input >= 0x80000002){parse_cpuid_ext_2();}
    if(cpuid.max_ext_input >= 0x80000006){parse_cpuid_ext_6();}
}
